iT邦幫忙

2023 iThome 鐵人賽

DAY 3
0
自我挑戰組

JS 加強筆記系列 第 3

Day 03:callbacks 的錯誤處理及問題

  • 分享至 

  • xImage
  •  

callback 中的 callback

昨天寫到在 loadScript 的例子中加入 callback,處理非同步完成後要做的事。那麼如果想要在載入檔案後再載入其他檔案,就會在 callback 中呼叫第二次 loadScript

loadScript('/my/script.js', function(script) {
    alert(`Cool, the ${script.src} is loaded`);
    
    loadScript('/my/script2.js', function(script) {
        alert(`Cool, the second script is loaded`);
    });
});

以此類推,如果要載入很多檔案,就會變成多層的結構,開始不好閱讀。不過這裡還只算是回呼地獄的大門口,還有一個問題還沒討論,那就是要考慮出錯的情況。

錯誤處理

如果幫 loadScript 函式加入錯誤處理,可以使用同一個 callback,成功的時候呼叫 callback(null, script),否則呼叫 callback(error)

function loadScript(src, callback) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => callback(null, script);
    script.onerror = () => callback(new Error(`Script load error for ${src}`));

    document.head.append(script);
}

實際使用:

loadScript('/my/script.js', function(error, script) {
    if (error) {
        // 處理錯誤
    } else {
        // script 成功載入
    }
});

這種方式是優先處理錯誤的 callback (error-first callback),它的想法是:

  1. callback 第一個參數留給錯誤,如果報錯就呼叫callback(error)
  2. 第二個及後面的參數則是成功結果,成功時呼叫 callback(null, arg1, arg2...)

回呼地獄

結合上述的巢狀結構和錯誤處理,非同步的任務一多,就可以如願 (?) 進到回呼地獄:

loadScript('1.js', function(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ...
        loadScript('2.js', function(error, script) {
            if (error) {
                handleError(error);
            } else {
                // ...
                loadScript('3.js', function(error, script) {
                    if (error) {
                        handleError(error);
                    } else {
                        // ...這裡繼續做所有 script 都載入後要做的事
                    }
                });
            }
        });
    }
});

而且可以想像一下程式碼中 ... 的地方可能包含其他要處理的邏輯,就會更複雜。

下方的例子把每個 callback 獨立拆出來避免無限巢狀,但這樣還是不太好,因為:

  • 函式內容可能不斷重複。
  • 函式只是為了避免巢狀而存在,本身不會在其他地方呼叫,複用價值不高。
  • 程式碼距離越拉越開,不易閱讀。
loadScript('1.js', step1);

function step1(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ...
        loadScript('2.js', step2);
    }
}

function step2(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ...
        loadScript('3.js', step3);
    }
}

function step3(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ...continue after all scripts are loaded (*)
    }
}

接下來就會開始寫到 promise 如何避免這些問題。

[附註] 回呼地獄 (callback hell)、波動拳、pyramid of doom 指的都是同樣的東西,感覺崩潰的事物很能激發人的想像力。


上一篇
Day 02:從 callbacks 開始
下一篇
Day 04:promise 結構及特性
系列文
JS 加強筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言